const fs = require('fs');
const path = require('path');
const os = require('os');
const { exec, execSync } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

/**
 * 自动分析MP3文件并进行音量正常化到0dB峰值
 * 这个脚本使用ffmpeg命令行来分析和调整MP3文件的音量，使波形尽量填满但不失真
 */

// 目标峰值电平为0dB，使波形尽量填满但不失真
const TARGET_PEAK_DB = 0;

/**
 * 分析音频文件的峰值电平
 * @param {string} filePath - MP3文件路径
 * @returns {Promise<number>} - 返回测量的峰值电平(dB)
 */
async function analyzeLoudness(filePath) {
  try {
    return new Promise((resolve, reject) => {
      const { spawn } = require('child_process');
      
      // 使用volumedetect过滤器测量峰值电平
      const args = [
        '-i', filePath,
        '-af', 'volumedetect',
        '-f', 'null',
        '-'
      ];
      
      const fileBaseName = path.basename(filePath);
      console.log(`[开始] 分析文件峰值电平: ${fileBaseName}`);
      
      const process = spawn('ffmpeg', args);
      
      let stderrData = '';
      let isCompleted = false;
      
      process.stderr.on('data', (data) => {
        stderrData += data.toString();
      });
      
      // 设置超时
      const timeout = setTimeout(() => {
        if (!isCompleted) {
          isCompleted = true;
          console.log(`[超时] 分析进程超时: ${fileBaseName}`);
          try {
            process.kill('SIGKILL');
          } catch (e) {
            console.log(`[错误] 无法杀死分析进程: ${fileBaseName}`, e.message);
          }
          reject(new Error(`分析文件 ${fileBaseName} 峰值电平超时`));
        }
      }, 300000); // 5分钟超时
      
      process.on('close', (code) => {
        console.log(`[事件:close] 分析进程关闭: ${fileBaseName}, 代码: ${code}`);
        if (isCompleted) return;
        isCompleted = true;
        clearTimeout(timeout);
        
        if (code === 0) {
          // 从输出中提取峰值电平信息
          const maxVolumeMatch = stderrData.match(/max_volume: ([-\d.]+) dB/);
          if (!maxVolumeMatch) {
            console.log(`[错误] 无法从ffmpeg输出中提取峰值电平信息: ${fileBaseName}`);
            reject(new Error('无法从ffmpeg输出中提取峰值电平信息'));
            return;
          }
          
          try {
            const peakLevel = parseFloat(maxVolumeMatch[1]);
            console.log(`[成功] 分析完成: ${fileBaseName}, 峰值电平: ${peakLevel.toFixed(2)} dB`);
            resolve(peakLevel);
          } catch (error) {
            console.log(`[错误] 解析峰值电平信息失败: ${fileBaseName}`, error.message);
            reject(new Error(`解析峰值电平信息失败: ${error.message}`));
          }
        } else {
          console.log(`[错误] ffmpeg分析进程以非零代码退出: ${fileBaseName}, 代码: ${code}`);
          reject(new Error(`ffmpeg分析进程以代码 ${code} 退出，错误: ${stderrData.slice(-500)}`));
        }
      });
      
      process.on('exit', (code) => {
        console.log(`[事件:exit] 分析进程退出: ${fileBaseName}, 代码: ${code}`);
        if (isCompleted) return;
        isCompleted = true;
        clearTimeout(timeout);
        
        if (code === 0) {
          // 尝试解析输出
          try {
            const maxVolumeMatch = stderrData.match(/max_volume: ([-\d.]+) dB/);
            if (maxVolumeMatch) {
              const peakLevel = parseFloat(maxVolumeMatch[1]);
              console.log(`[成功] 分析完成(exit事件): ${fileBaseName}, 峰值电平: ${peakLevel.toFixed(2)} dB`);
              resolve(peakLevel);
            } else {
              console.log(`[错误] exit事件: 无法从ffmpeg输出中提取峰值电平信息: ${fileBaseName}`);
              reject(new Error('exit事件: 无法从ffmpeg输出中提取峰值电平信息'));
            }
          } catch (error) {
            console.log(`[错误] exit事件: 解析峰值电平信息失败: ${fileBaseName}`, error.message);
            reject(new Error(`exit事件: 解析峰值电平信息失败: ${error.message}`));
          }
        } else {
          console.log(`[错误] ffmpeg分析进程(exit事件)异常退出: ${fileBaseName}, 代码: ${code}`);
          reject(new Error(`ffmpeg分析进程exit事件，代码: ${code}`));
        }
      });
      
      process.on('error', (err) => {
        console.log(`[事件:error] 分析进程错误: ${fileBaseName}`, err.message);
        if (isCompleted) return;
        isCompleted = true;
        clearTimeout(timeout);
        reject(new Error(`启动ffmpeg分析进程错误: ${err.message}`));
      });
    });
  } catch (error) {
    console.error(`[捕获错误] 分析文件 ${path.basename(filePath)} 时出错:`, error);
    throw error;
  }
}

/**
 * 调整音频文件的音量
 * @param {string} inputPath - 输入文件路径
 * @param {string} outputPath - 输出文件路径
 * @param {number} gainDB - 需要应用的增益(dB)
 * @returns {Promise<void>}
 */
async function normalizeAudio(inputPath, outputPath, gainDB) {
  try {
    // 使用直接的子进程命令行执行方式，避免spawn可能的问题
    const { execFile } = require('child_process');
    const util = require('util');
    const execFilePromise = util.promisify(execFile);
    
    const fileBaseName = path.basename(inputPath);
    console.log(`[开始] 处理文件: ${fileBaseName}, 增益: ${gainDB.toFixed(2)}dB`);
    
    // 获取原始文件的时间戳
    const originalStats = fs.statSync(inputPath);
    const originalAccessTime = originalStats.atime;
    const originalModifiedTime = originalStats.mtime;
    const originalCreationTime = originalStats.birthtime;
    
    // 创建临时输出文件路径
    const outputDir = path.dirname(outputPath);
    const outputFileName = path.basename(outputPath);
    const outputExt = path.extname(outputFileName);
    const outputBaseName = path.basename(outputFileName, outputExt);
    const tempOutputPath = path.join(outputDir, `${outputBaseName}_bootleg_link_tmp${outputExt}`);

    // 为了保险起见，先删除可能存在的临时文件
    try {
      if (fs.existsSync(tempOutputPath)) {
        fs.unlinkSync(tempOutputPath);
        console.log(`[信息] 已删除已存在的临时文件: ${path.basename(tempOutputPath)}`);
      }
    } catch (cleanupError) {
      console.error(`[警告] 删除已存在的临时文件失败: ${path.basename(tempOutputPath)}`, cleanupError.message);
    }

    // 准备参数数组 - 移除引号，让execFile正确处理路径
    const args = [
      '-i', inputPath,
      '-af', `volume=${gainDB}dB`,
      '-c:a', 'libmp3lame',
      '-q:a', '0',
      '-map_metadata', '0',
      '-y', // 覆盖输出文件
      tempOutputPath
    ];
    
    // 记录开始时间以计算处理时长
    const startTime = Date.now();
    console.log(`[命令] ffmpeg ${args.join(' ')}`);
    
    try {
      // 使用较短的超时时间，避免长时间挂起
      const { stdout, stderr } = await execFilePromise('ffmpeg', args, { 
        timeout: 600000, // 10分钟超时
        maxBuffer: 10 * 1024 * 1024 // 10MB缓冲区，避免输出溢出
      });
      
      // 检查临时文件是否存在且大小正常
      const stats = fs.statSync(tempOutputPath);
      if (stats.size > 0) {
        // 将临时文件重命名为最终输出文件
        fs.renameSync(tempOutputPath, outputPath);
        
        // 恢复原始文件的时间戳
        fs.utimesSync(outputPath, originalAccessTime, originalModifiedTime);
        console.log(`[信息] 已恢复文件时间戳: ${fileBaseName}`);
        
        const duration = ((Date.now() - startTime) / 1000).toFixed(1);
        console.log(`[成功] 处理完成: ${fileBaseName}, 应用增益: ${gainDB.toFixed(2)}dB, 耗时: ${duration}秒`);
        return;
      } else {
        throw new Error('临时输出文件大小为0');
      }
    } catch (error) {
      const duration = ((Date.now() - startTime) / 1000).toFixed(1);
      
      if (error.killed) {
        console.error(`[超时] 处理超时: ${fileBaseName}, 耗时: ${duration}秒`);
        // 清理临时文件
        try {
          if (fs.existsSync(tempOutputPath)) {
            fs.unlinkSync(tempOutputPath);
          }
        } catch (cleanupError) {
          console.error(`[警告] 清理临时文件失败: ${tempOutputPath}`, cleanupError.message);
        }
        throw new Error(`处理文件 ${fileBaseName} 超时`);
      }
      
      // 检查临时文件是否存在且大小正常，如果是可能处理成功了
      try {
        const stats = fs.statSync(tempOutputPath);
        if (stats.size > 0) {
          console.log(`[注意] 尽管出错，但临时文件存在且不为空: ${fileBaseName}, 大小: ${stats.size} 字节`);
          // 将临时文件重命名为最终输出文件
          fs.renameSync(tempOutputPath, outputPath);
          
          // 恢复原始文件的时间戳
          fs.utimesSync(outputPath, originalAccessTime, originalModifiedTime);
          console.log(`[信息] 已恢复文件时间戳: ${fileBaseName}`);
          
          console.log(`[成功] 处理可能已完成: ${fileBaseName}, 应用增益: ${gainDB.toFixed(2)}dB, 耗时: ${duration}秒`);
          return;
        }
      } catch (statError) {
        // 临时输出文件不存在或无法访问
      }
      
      // 清理临时文件
      try {
        if (fs.existsSync(tempOutputPath)) {
          fs.unlinkSync(tempOutputPath);
        }
      } catch (cleanupError) {
        console.error(`[警告] 清理临时文件失败: ${tempOutputPath}`, cleanupError.message);
      }
      
      console.error(`[错误] 处理失败: ${fileBaseName}, 代码: ${error.code}, 耗时: ${duration}秒`);
      console.error(`[错误详情] ${error.message}`);
      if (error.stderr) {
        console.error(`[错误输出] ${error.stderr.substring(0, 500)}...`);
      }
      throw error;
    }
  } catch (error) {
    console.error(`[捕获错误] 处理文件 ${path.basename(inputPath)} 时出错:`, error);
    throw error;
  }
}

/**
 * 处理单个MP3文件
 * @param {string} filePath - MP3文件路径
 * @param {string} outputDir - 输出目录
 * @returns {Promise<void>}
 */
async function processFile(filePath, outputDir) {
  try {
    const fileName = path.basename(filePath);
    const outputPath = path.join(outputDir, fileName);
    
    console.log(`\n[处理文件] 开始处理: ${fileName}`);
    const measuredPeakDB = await analyzeLoudness(filePath);
    console.log(`[信息] 测量的峰值电平: ${measuredPeakDB.toFixed(2)} dB`);
    
    // 计算需要应用的增益以达到0dB峰值
    const HEADROOM = 0.0;
    const gainDB = TARGET_PEAK_DB - measuredPeakDB - HEADROOM;
    
    console.log(`[信息] 计算增益: ${gainDB.toFixed(2)}dB, 文件: ${fileName}`);
    
    // 如果增益是0.0就跳过处理
    if (Math.abs(gainDB) < 0.01) {
      console.log(`[跳过] 增益接近0dB (${gainDB.toFixed(2)}dB), 无需处理: ${fileName}`);
      return true;
    }
    
    await normalizeAudio(filePath, outputPath, gainDB);
    console.log(`[完成] 文件处理完成: ${fileName}`);
    return true;
  } catch (error) {
    console.error(`[处理失败] 文件处理失败: ${path.basename(filePath)}`, error);
    return false;
  }
}

/**
 * 递归处理目录中的所有MP3文件
 * @param {string} inputDir - 输入目录
 * @returns {Promise<void>}
 */
async function processDirectory(inputDir) {
  try {
    // 递归获取所有MP3文件
    const mp3Files = [];
    function traverseDirectory(dir) {
      try {
        const items = fs.readdirSync(dir);
        for (const item of items) {
          try {
            // 跳过以._开头的文件（macOS在FAT32等文件系统上创建的元数据文件）
            if (item.startsWith('._')) {
              console.log(`[跳过] 跳过元数据文件: ${item}`);
              continue;
            }
            
            const fullPath = path.join(dir, item);
            const stat = fs.statSync(fullPath);
            
            if (stat.isDirectory()) {
              // 递归处理子目录
              traverseDirectory(fullPath);
            } else if (stat.isFile() && path.extname(item).toLowerCase() === '.mp3') {
              mp3Files.push(fullPath);
              console.log(`[发现] MP3文件: ${fullPath}`);
            }
          } catch (err) {
            console.error(`处理文件/目录时出错: ${item}`, err);
            // 继续处理其他文件
          }
        }
      } catch (err) {
        console.error(`无法读取目录: ${dir}`, err);
      }
    }
    
    traverseDirectory(inputDir);
    console.log(`在目录 ${inputDir} 及其子目录中找到 ${mp3Files.length} 个MP3文件`);
    
    // 获取CPU核心数
    const cpuCount = os.cpus().length;
    console.log(`使用 ${cpuCount} 个核心进行并行处理`);
    
    // 创建一个信号量来控制并发执行
    let runningTasks = 0;
    let completedTasks = 0;
    const totalTasks = mp3Files.length;
    let fileIndex = 0;
    
    return new Promise((resolve) => {
      // 处理下一个文件的函数
      async function processNextFile() {
        if (fileIndex >= totalTasks) {
          // 所有文件已经分配任务
          if (completedTasks >= totalTasks) {
            // 所有任务完成
            console.log('所有文件处理完成!');
            resolve();
          }
          return;
        }
        
        const currentFile = mp3Files[fileIndex++];
        runningTasks++;
        
        try {
          await processFile(currentFile, path.dirname(currentFile));
          completedTasks++;
          console.log(`进度: ${completedTasks}/${totalTasks} (${Math.round(completedTasks/totalTasks*100)}%)`);
        } catch (error) {
          console.error(`处理文件失败: ${path.basename(currentFile)}`, error);
          completedTasks++;
        } finally {
          runningTasks--;
          // 处理下一个文件
          processNextFile();
        }
      }
      
      // 启动初始的处理任务，数量等于CPU核心数
      for (let i = 0; i < cpuCount && i < totalTasks; i++) {
        processNextFile();
      }
    });
  } catch (error) {
    console.error('处理目录时出错:', error);
  }
}

// 命令行参数处理
if (require.main === module) {
  const args = process.argv.slice(2);
  
  if (args.length < 1) {
    console.log(`
使用方法:
  单个文件: node autogain.js -f <mp3文件路径>
  整个目录: node autogain.js -d <输入目录>

该脚本会将MP3文件音量正常化到0dB峰值(不失真)，使音频波形尽量填满。
    `);
    process.exit(1);
  }
  
  const mode = args[0];
  
  if (mode === '-f' && args.length >= 2) {
    // 处理单个文件
    const filePath = args[1];
    processFile(filePath, path.dirname(filePath))
      .then(() => console.log('处理完成'))
      .catch(err => console.error('处理出错:', err));
  } else if (mode === '-d' && args.length >= 2) {
    // 处理整个目录
    const inputDir = args[1];
    processDirectory(inputDir)
      .then(() => console.log('所有文件处理完成'))
      .catch(err => console.error('处理目录出错:', err));
  } else {
    console.log('无效的参数。使用 -f <文件路径> 或 -d <目录路径>');
    process.exit(1);
  }
}

// 导出函数以便其他模块使用
module.exports = {
  analyzeLoudness, // 现在分析的是峰值电平而非LUFS
  normalizeAudio,
  processFile,
  processDirectory,
  TARGET_PEAK_DB // 导出目标峰值
};
